/**********************************************************
 * ActionScript Development Tool 
 * Copyright (C) 2005 asdt.org 
 * 
 * http://www.asdt.org
 * http://sourceforge.net/projects/aseclipseplugin/
 *
 * This program is free software; 
 * you can redistribute it and/or modify it under the terms of 
 * the GNU General Public License as published by the 
 * Free Software Foundation; either version 2 of the License, 
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the 
 * Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 
 * 
 **********************************************************/
package ca.scotthyndman.as2haxe.dom;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import ca.scotthyndman.as2haxe.dom.visitor.IVisitable;
import ca.scotthyndman.as2haxe.dom.visitor.IVisitor;

/**
 * @author Peter Schreiber
 * 
 */
public class ASTypeDeclaration extends ASTNode implements IVisitable,
		IScopeElement {

	/**
	 * The "interface" structural property of this node type.
	 */
	public static final ASSimplePropertyDescriptor INTERFACE_PROPERTY = new ASSimplePropertyDescriptor(
			ASTypeDeclaration.class, "interface", boolean.class, true);

	/**
	 * The "name" structural property of this node type.
	 */
	public static final ASChildPropertyDescriptor NAME_PROPERTY = new ASChildPropertyDescriptor(
			ASTypeDeclaration.class, "name", ASQualifiedName.class, true);

	/**
	 * The "superclass" structural property of this node type
	 */
	public static final ASChildPropertyDescriptor SUPERCLASS_TYPE_PROPERTY = new ASChildPropertyDescriptor(
			ASTypeDeclaration.class, "superclass", ASType.class, false);

	/**
	 * The "superInterfaces" structural property of this node type
	 */
	public static final ASChildListPropertyDescriptor SUPER_INTERFACES_PROPERTY = new ASChildListPropertyDescriptor(
			ASTypeDeclaration.class, "superInterfaces", ASType.class);

	/**
	 * The "superInterfaces" structural property of this node type
	 */
	public static final ASChildListPropertyDescriptor BODY_DECLARATIONS_PROPERTY = new ASChildListPropertyDescriptor(
			ASTypeDeclaration.class, "superInterfaces", ASBodyDeclaration.class);

	/**
	 * A list of property descriptors (element type:
	 * {@link ASPropertyDescriptor}).
	 */
	private static final List<ASPropertyDescriptor> PROPERTY_DESCRIPTORS;

	static {
		List<Object> propertyList = new ArrayList<Object>(6);
		createPropertyList(ASTypeDeclaration.class, propertyList);
		addProperty(INTERFACE_PROPERTY, propertyList);
		addProperty(NAME_PROPERTY, propertyList);
		addProperty(SUPERCLASS_TYPE_PROPERTY, propertyList);
		addProperty(SUPER_INTERFACES_PROPERTY, propertyList);
		addProperty(BODY_DECLARATIONS_PROPERTY, propertyList);
		PROPERTY_DESCRIPTORS = reapPropertyList(propertyList);
	}

	/**
	 * Returns a list of structural property descriptors for this node type.
	 * Clients must not modify the result.
	 * 
	 * @return a list of property descriptors (element type:
	 *         {@link ASPropertyDescriptor})
	 */
	public static List<ASPropertyDescriptor> propertyDescriptors() {
		return PROPERTY_DESCRIPTORS;
	}

	//
	// ======== MEMBERS
	//

	private boolean isInterface;

	private ASQualifiedName typeName;

	private ASType optionalSuperclassType;

	private List<ASTNode> interfaceTypes = new ASTNode.NodeList(
			SUPER_INTERFACES_PROPERTY);

	private List<ASTNode> bodyDeclarations = new NodeList(
			BODY_DECLARATIONS_PROPERTY);

	private List<ASComment> comments = new ArrayList<ASComment>();

	//
	// ======== SCOPING AND OUTPUT RELATED VARIABLES
	// 
	// TODO Reconsider whether these are required
	//

	private List<ASTNode> codeRegions = new ArrayList<ASTNode>();;

	private List<ASVariableDeclaration> scopedVariables = new ArrayList<ASVariableDeclaration>();

	private List<ASFunction> scopedFunctions = new ArrayList<ASFunction>();

	//
	// ======== CONSTRUCTION
	//

	/**
	 * Declares a new type.
	 */
	public ASTypeDeclaration(AST ast) {
		super(ast);

		ast.addType(this);
	}

	//
	// ======== GETTERS AND SETTERS
	//

	/**
	 * Returns whether this type declaration declares a class or an interface.
	 * 
	 * @return <code>true</code> if this is an interface declaration, and
	 *         <code>false</code> if this is a class declaration
	 */
	public boolean isInterface() {
		return this.isInterface;
	}

	/**
	 * Sets whether this type declaration declares a class or an interface.
	 * 
	 * @param isInterface
	 *            <code>true</code> if this is an interface declaration, and
	 *            <code>false</code> if this is a class declaration
	 */
	public void setInterface(boolean isInterface) {
		preValueChange(INTERFACE_PROPERTY);
		this.isInterface = isInterface;
		postValueChange(INTERFACE_PROPERTY);
	}

	/**
	 * Returns the name of the type declared in this type declaration.
	 * 
	 * @return the type name node
	 */
	public ASQualifiedName getName() {
		return typeName;
	}

	/**
	 * Sets the name of the type declared in this type declaration to the given
	 * name.
	 * 
	 * @param typeName
	 *            the new type name
	 * @exception IllegalArgumentException
	 *                if:
	 *                <ul>
	 *                <li>the node belongs to a different AST</li>
	 *                <li>the node already has a parent</li>
	 *                </ul>
	 */
	public void setName(ASQualifiedName typeName) {
		if (typeName == null) {
			throw new IllegalArgumentException();
		}

		ASTNode oldChild = this.typeName;
		preReplaceChild(oldChild, typeName, NAME_PROPERTY);
		this.typeName = typeName;
		postReplaceChild(oldChild, typeName, NAME_PROPERTY);
	}

	/**
	 * Returns the live ordered list of superinterfaces of this type declaration .
	 * For a class declaration, these are the interfaces that this class
	 * implements; for an interface declaration, these are the interfaces that
	 * this interface extends.
	 * 
	 * @return the live list of interface types (element type:
	 *         <code>ASType</code>)
	 */
	public List<ASTNode> getInterfaceTypes() {
		return interfaceTypes;
	}

	/**
	 * Returns the superclass declared in this type declaration, or
	 * <code>null</code> if there is none (added in JLS3 API).
	 * <p>
	 * Note that this child is not relevant for interface declarations (although
	 * it does still figure in subtree equality comparisons).
	 * </p>
	 * 
	 * @return the superclass type node, or <code>null</code> if there is none
	 * @exception UnsupportedOperationException
	 *                if this operation is used in a JLS2 AST
	 * @since 3.1
	 */
	public ASType getSuperClassType() {
		return this.optionalSuperclassType;
	}

	/**
	 * Sets or clears the superclass declared in this type declaration.
	 * <p>
	 * Note that this child is not relevant for interface declarations (although
	 * it does still figure in subtree equality comparisons).
	 * </p>
	 * 
	 * @param superclassType
	 *            the superclass type node, or <code>null</code> if there is
	 *            none
	 * @exception IllegalArgumentException
	 *                if:
	 *                <ul>
	 *                <li>the node belongs to a different AST</li>
	 *                <li>the node already has a parent</li>
	 *                </ul>
	 */
	public void setSuperClassType(ASType superclassType) {
		ASTNode oldChild = this.optionalSuperclassType;
		preReplaceChild(oldChild, superclassType, SUPERCLASS_TYPE_PROPERTY);
		this.optionalSuperclassType = superclassType;
		postReplaceChild(oldChild, superclassType, SUPERCLASS_TYPE_PROPERTY);
	}

	/**
	 * Returns this type's comments.
	 * 
	 * @return the list of comments.
	 */
	public List<ASComment> comments() {
		return comments;
	}

	/**
	 * Returns the live ordered list of body declarations of this type
	 * declaration.
	 * 
	 * @return the live list of body declarations (element type:
	 *         <code>ASBodyDeclaration</code>)
	 */
	public List<ASTNode> bodyDeclarations() {
		return this.bodyDeclarations;
	}

	//
	// ======== INTERNAL GETTER SETTER
	//

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	final Object internalGetSetObjectProperty(
			ASSimplePropertyDescriptor property, boolean get, Object value) {
		// allow default implementation to flag the error
		return super.internalGetSetObjectProperty(property, get, value);
	}

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	final ASTNode internalGetSetChildProperty(
			ASChildPropertyDescriptor property, boolean get, ASTNode child) {
		if (property == NAME_PROPERTY) {
			if (get) {
				return getName();
			} else {
				setName((ASQualifiedName) child);
				return null;
			}
		}
		if (property == SUPERCLASS_TYPE_PROPERTY) {
			if (get) {
				return getSuperClassType();
			} else {
				setSuperClassType((ASType) child);
				return null;
			}
		}
		// allow default implementation to flag the error
		return super.internalGetSetChildProperty(property, get, child);
	}

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	boolean internalGetSetBooleanProperty(ASSimplePropertyDescriptor property,
			boolean get, boolean value) {
		if (property == INTERFACE_PROPERTY) {
			if (get) {
				return isInterface();
			} else {
				setInterface(value);
				return false;
			}
		}

		return super.internalGetSetBooleanProperty(property, get, value);
	}

	/*
	 * Internal
	 */
	final List<ASTNode> internalGetChildListProperty(
			ASChildListPropertyDescriptor property) {
		if (property == BODY_DECLARATIONS_PROPERTY) {
			return bodyDeclarations();
		}
		if (property == SUPER_INTERFACES_PROPERTY) {
			return getInterfaceTypes();
		}

		// allow default implementation to flag the error
		return super.internalGetChildListProperty(property);
	}

	//
	// ======== FOR FORMATTING
	//

	/**
	 * Returns a list of code elements sorted by their region offset.
	 * 
	 * @return A sorted list of AST nodes
	 */
	public List<ASTNode> getCodeRegions() {
		List<ASTNode> regions = new ArrayList<ASTNode>(codeRegions);
		Collections.sort(regions, new CodeRegionComparator());
		return regions;
	}

	/**
	 * Get code regions within a specific range.
	 * 
	 * @param lower
	 *            the lower bound
	 * @param upper
	 *            the upper bound (-1 is no upper bound)
	 * @return the elements in the specified bounds
	 */
	public List<ASTNode> getCodeRegions(int lower, int upper) {
		List<ASTNode> regions = getCodeRegions();
		Set<ASTNode> toRemove = new HashSet<ASTNode>();

		if (upper == -1) {
			upper = regions.get(regions.size() - 1).getFullRegion().getOffset();
		}

		for (ASTNode r : regions) {
			if (r.getFullRegion().getOffset() < lower
					|| r.getFullRegion().getOffset() > upper) {
				toRemove.add(r);
			}
		}

		regions.removeAll(toRemove);

		return regions;
	}

	//
	// ======== HELPER FUNCTIONS
	//

	/**
	 * Find out if the class has a public static main method
	 * 
	 * @return
	 */
	public boolean isExecutable() {
		for (ASTNode decl : bodyDeclarations()) {
			if (decl instanceof ASMethodDeclaration) {
				ASMethodDeclaration meth = (ASMethodDeclaration) decl;
				if (meth.getFunction().getName().toString().equals("main")) {
					return true;
				}
			}
		}

		return false;
	}

	// public IVisitable getElementAt(int offset) {
	// // check, if we are within the object
	// IRegion fullRegion = getFullRegion();
	// if (offset >= fullRegion.getOffset()
	// && offset < (fullRegion.getLength() + fullRegion.getOffset())) {
	// // lets check for variables or functions
	// for (Iterator<ASFunction> iter = functions.iterator(); iter
	// .hasNext();) {
	// ASFunction element = iter.next();
	// if (offset >= element.getFullRegion().getOffset()
	// && offset < (element.getFullRegion().getLength() + element
	// .getFullRegion().getOffset())) {
	// return element;
	// }
	// }
	// for (Iterator<ASVariableDeclarationStatement> iter = variables
	// .iterator(); iter.hasNext();) {
	// ASVariableDeclarationStatement element = iter.next();
	// if (offset >= element.getFullRegion().getOffset()
	// && offset < (element.getFullRegion().getLength() + element
	// .getFullRegion().getOffset())) {
	// return element;
	// }
	// }
	// // check constructor
	// ASFunction element = constructor;
	// if (element != null
	// && element.getFullRegion() != null
	// && offset >= element.getFullRegion().getOffset()
	// && offset < (element.getFullRegion().getLength() + element
	// .getFullRegion().getOffset())) {
	// return element;
	// }
	// return null;
	// } else if (offset < fullRegion.getOffset()) {
	// // lets check imports
	// for (Iterator iter = getImports().iterator(); iter.hasNext();) {
	// ASImportDeclaration element = (ASImportDeclaration) iter.next();
	// if (offset >= element.getFullRegion().getOffset()
	// && offset < (element.getFullRegion().getLength() + element
	// .getFullRegion().getOffset())) {
	// return element;
	// }
	// }
	// return null;
	// } else {
	// return null;
	// }
	// }

	public boolean equals(Object obj) {
		if (obj instanceof ASTypeDeclaration) {
			ASTypeDeclaration type = (ASTypeDeclaration) obj;
			if (type.getName() != null && this.getName() != null) {
				return getName().equals(type.getName());
			}

			return type.getName() == getName();
		}

		return false;
	}

	public String toString() {
		String abstractTypeDescription = "";
		if (getName() != null && getName().getName() != null) {
			abstractTypeDescription = "t: " + getName().toString();
		}
		return abstractTypeDescription;
	}

	public String toStringWithPackage() {
		String abstractTypeDescription = "";
		if (getName() != null && getName().getQualifier() != null) {
			abstractTypeDescription = getName().getQualifier().toString() + "."
					+ getName().getName().toString();
		} else {
			abstractTypeDescription = getName().getName().toString();
		}
		return abstractTypeDescription;
	}

	//
	// ======== VISITABLE IMPLEMENTATION
	//

	/**
	 * Visitable implementation
	 * 
	 * @see ca.scotthyndman.as2haxe.dom.visitor.IVisitable#accept(org.asdt.core.dom.IAS2Visitor,
	 *      boolean)
	 */
	public void accept(IVisitor as2Visitor, boolean recursive) {
		recursive = as2Visitor.visit(this) && recursive;

		if (recursive) {
			// imports

			for (ASTNode bd : bodyDeclarations()) {
				bd.accept(as2Visitor, recursive);
			}
		}
	}

	//
	// ======== GETTING TYPES BY QUALIFIED NAME
	//

	/**
	 * Returns a type by name.
	 * 
	 * @param name
	 *            the type's name
	 * @return the type, or <code>null</code> if not found
	 */
	public static ASTypeDeclaration getType(String name) {
		ASPackage pkg;
		int idx = name.lastIndexOf('.');
		if (idx == -1) {
			pkg = ASPackage.getRootPackage();
		} else {
			pkg = ASPackage.getPackage(name.substring(0, idx));
			name = name.substring(idx + 1);
		}

		if (pkg == null) {
			return null;
		}

		return pkg.getTypesMap().get(name);
	}

	//
	// ======== SCOPING
	//

	public void addToScope(ASVariableDeclaration variable) {
		scopedVariables.add(variable);
	}

	public void addToScope(ASFunction function) {
		scopedFunctions.add(function);
	}

	public List<ASVariableDeclaration> getVariablesInScope() {
		return scopedVariables;
	}

	public List<ASFunction> getFunctionsInScope() {
		return scopedFunctions;
	}

	//
	// ======== HELPER CLASSES
	//

	class DomComparator implements Comparator {
		public int compare(Object arg0, Object arg1) {
			if (arg0 instanceof ASFunction && arg1 instanceof ASFunction) {
				ASFunction element1 = (ASFunction) arg0;
				ASFunction element2 = (ASFunction) arg1;
				return element1.getName().toString().compareToIgnoreCase(
						element2.getName().toString());
			} else if (arg0 instanceof ASVariableDeclarationStatement
					&& arg1 instanceof ASVariableDeclarationStatement) {
				ASVariableDeclarationStatement element1 = (ASVariableDeclarationStatement) arg0;
				ASVariableDeclarationStatement element2 = (ASVariableDeclarationStatement) arg1;
				return element1
						.getDeclaration().getName().toString()
						.compareToIgnoreCase(
								element2.getDeclaration().getName().toString());
			}
			return -1;
		}
	}

	class CodeRegionComparator implements Comparator<ICodeRegion> {

		public int compare(ICodeRegion o1, ICodeRegion o2) {
			return new Integer(o1.getFullRegion().getOffset()).compareTo(o2
					.getFullRegion().getOffset());
		}

	}
}